package util

import (
	"crypto/sha256"
	"encoding/base64"
	"errors"
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"strings"

	"gitee.com/hp-huiw/my-go-core/date"
	"github.com/google/uuid"
	"github.com/samber/lo"
	"golang.org/x/crypto/bcrypt"
)

const (
	BooleanTrue = "1"
)

// GenerateUUID generates a new UUID string.
func GenerateUUID() string {
	return uuid.New().String()
}

// GenerateUUIDWithoutDashes generates a new UUID string without dashes.
func GenerateUUIDWithoutDashes() string {
	return strings.ReplaceAll(GenerateUUID(), "-", "")
}

// SliceExcludes excludes every item in target slice from source slice.
func SliceExcludes[T comparable](source []T, target []T) []T {
	return lo.Reject(source, func(item T, _ int) bool {
		return lo.Contains(target, item) // Reject items in target
	})
}

// CopyMap copies each item from input map.
// This is NOT a deep copy.
func CopyMap(m map[string]any) map[string]any {
	result := map[string]any{}

	for key, value := range m {
		result[key] = value
	}

	return result
}

// InsertSlice inserts an item into a slice at specific position.
func InsertSlice[T any](input []T, item T, index int) []T {
	return append(input[:index], append([]T{item}, input[index:]...)...)
}

// ToString converts str to string type.
// NO panic if str is nil.
func ToString(str any) string {
	return fmt.Sprintf("%v", str)
}

// IsBooleanTrue checks if input int/string is value of boolean true.
func IsBooleanTrue(p any) bool {
	return ToString(p) == BooleanTrue
}

// StringToInt converts the input string to int type.
func StringToInt(str string) (int, error) {
	return strconv.Atoi(str)
}

// StringToInt64 converts the input string to int64 type.
func StringToInt64(str string) (int64, error) {
	return strconv.ParseInt(str, 10, 64)
}

// StringToFloat64 converts the input string to float64 type.
func StringToFloat64(str string) (float64, error) {
	return strconv.ParseFloat(str, 64)
}

// IntToString converts the input int value to string type.
func IntToString(value int) string {
	return strconv.Itoa(value)
}

// Int64ToString converts the input int64 value to string type.
func Int64ToString(value int64) string {
	return strconv.FormatInt(value, 10)
}

// IsEmptyString checks if str is an empty string.
func IsEmptyString(str string) bool {
	return strings.TrimSpace(str) == "" || strings.EqualFold(str, "null")
}

func AnyPtr[T any](v T) *T {
	return &v
}

// PtrEqual compares two pointers for equality.
// Returns true if both are nil, both point to the same memory address,
// or both point to values that are equal.
func PtrEqual[T comparable](a, b *T) bool {
	if a == nil || b == nil {
		return a == b
	}

	return *a == *b
}

// Sum sums all int values in the list.
func Sum(list []int) int {
	result := 0

	for _, v := range list {
		result += v
	}

	return result
}

// Reverses each int position in the slice.
// func ReverseIntSlice(list []int) []int {
// 	length := len(list)
// 	result := make([]int, length)

// 	for index, item := range list {
// 		result[length-index-1] = item
// 	}

// 	return result
// }

// NextRandomIntInRange gets the next random int value in provided range.
func NextRandomIntInRange(min int, max int) int {
	// Top-level functions, such as Float64 and Int, use a default shared Source that produces
	//a deterministic sequence of values each time a program is run. Use the Seed function to
	//initialize the default Source if different behavior is required for each run.
	// https://golang.org/pkg/math/rand/
	// rand.Seed(GetCurrentTime().UnixNano())
	rand.New(rand.NewSource(date.GetCurrentDate().UnixNano()))

	return rand.Intn(max-min+1) + min
}

func HashPassword(rawPassword string) (string, error) {
	hash, err := bcrypt.GenerateFromPassword([]byte(rawPassword), bcrypt.DefaultCost)

	if err != nil {
		return "", err
	}

	return string(hash), nil
}

func CheckPassword(rawPassword string, encodedPassword string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(encodedPassword), []byte(rawPassword))

	return err == nil
}

// EncryptWithoutKey encrypts input string without key.
// Normally it used to encrypt password before saving.
func EncryptWithoutKey(content string) string {
	bytes := sha256.Sum256([]byte(content))
	return base64.StdEncoding.EncodeToString(bytes[:])
}

// PathExists checks if input path exists.
func PathExists(path string) bool {
	_, err := os.Stat(path)
	return !errors.Is(err, os.ErrNotExist)
}
